import { color } from '../../core/color.js';
import fs from 'node:fs/promises';
import { DEFAULT_CONFIG } from '../../core/constants.js';
import { getConfig } from '../../core/config.js';
import { createRequire } from 'node:module';
import { exec as exec$1, spawn, execSync } from 'node:child_process';
import { promisify } from 'node:util';
import { logger } from '../../core/logger.js';
import path from 'node:path';
import os from 'node:os';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { IS_BUN_PM } from './runtime-utils.js';
import { existsSync } from 'node:fs';

const __DIRNAME = path.dirname(fileURLToPath(import.meta.url));
const PackageDir = path.resolve(__DIRNAME, "..", "..", "..");
const execAsync = promisify(exec$1);
const require2 = createRequire(import.meta.url);
const packageJson = require2("../../../package.json");
function cleanTempDir() {
  try {
    return fs.rm(getTempDir(), { force: true, recursive: true });
  } catch (error) {
    if (hasProperties(error, ["code"]) && error.code !== "ENOENT") {
      throw error;
    }
  }
}
function getPodStatusColor(status) {
  if (["Deploying", "Updating"].includes(status)) {
    return color.cyan;
  } else if (["Idle", "Stopped"].includes(status)) {
    return color.dim;
  } else if (["Online", "Ready"].includes(status)) {
    return color.green;
  } else {
    return color.red;
  }
}
function getTempDir() {
  return path.join(process.cwd(), ".robo", "temp");
}
async function getRoboPackageJson() {
  const packageJsonPath = path.join(process.cwd(), "package.json");
  const packageJson2 = JSON.parse(await fs.readFile(packageJsonPath, "utf-8") ?? "{}");
  return packageJson2;
}
function exec(command, options) {
  return new Promise((resolve, reject) => {
    let childProcess;
    const spawnOptions = {
      env: { ...process.env, FORCE_COLOR: "1" },
      shell: IS_WINDOWS,
      stdio: "inherit",
      ...options ?? {}
    };
    logger.debug(`${IS_WINDOWS ? "$" : ">"} ${color.bold(JSON.stringify(command))}`);
    if (IS_WINDOWS) {
      const str = Array.isArray(command) ? command.map((c) => c.includes(" ") ? `"${c}"` : c).join(" ") : command;
      childProcess = spawn(str, spawnOptions);
    } else {
      const args = Array.isArray(command) ? command : command.split(" ");
      childProcess = spawn(args.shift(), args, spawnOptions);
    }
    childProcess.on("error", reject);
    childProcess.on("close", (code) => {
      if (code === 0) {
        resolve();
      } else {
        reject(`Command exited with code ${code}`);
      }
    });
  });
}
async function filterExistingPaths(paths, basePath = process.cwd()) {
  const result = [];
  for (const relativePath of paths) {
    const absolutePath = path.resolve(basePath, relativePath);
    try {
      await fs.stat(absolutePath);
      result.push(relativePath);
    } catch (err) {
    }
  }
  return result;
}
async function copyDir(src, dest, excludeExtensions, excludePaths, overwrite = true) {
  await fs.mkdir(dest, { recursive: true });
  const entries = await fs.readdir(src);
  for (const entry of entries) {
    const srcPath = path.join(src, entry);
    const destPath = path.join(dest, entry);
    const entryStat = await fs.stat(srcPath);
    const entryExt = path.extname(srcPath);
    const resolvedPath = path.resolve(process.cwd(), srcPath);
    const isIgnored = excludePaths.some((p) => resolvedPath.startsWith(p));
    if (isIgnored || excludeExtensions.includes(entryExt)) {
      continue;
    } else if (entryStat.isDirectory()) {
      await copyDir(srcPath, destPath, excludeExtensions, excludePaths);
    } else if (overwrite || !existsSync(destPath)) {
      await fs.copyFile(srcPath, destPath);
    }
  }
}
function copyToClipboard(text) {
  const platform = os.platform();
  try {
    if (platform === "darwin") {
      execSync(`echo "${text}" | pbcopy`);
    } else if (platform === "win32") {
      execSync(`echo ${text} | clip`);
    } else {
      execSync(`echo "${text}" | xclip -selection clipboard`);
    }
  } catch (error) {
    console.error("Failed to copy to clipboard:", error);
  }
}
function openBrowser(url) {
  const platform = os.platform();
  let command;
  if (platform === "win32") {
    command = `start "${url}"`;
  } else if (platform === "darwin") {
    command = `open ${url}`;
  } else {
    command = `xdg-open ${url}`;
  }
  execSync(command);
}
async function findNodeModules(basePath) {
  const nodeModulesPath = path.join(basePath, "node_modules");
  try {
    await fs.access(nodeModulesPath);
    return nodeModulesPath;
  } catch (error) {
    const parentPath = path.resolve(basePath, "..");
    if (parentPath !== basePath) {
      return findNodeModules(parentPath);
    } else {
      return null;
    }
  }
}
async function findPackagePath(packageName, currentPath) {
  const nodeModulesPath = await findNodeModules(currentPath);
  if (!nodeModulesPath) {
    logger.debug(`Could not find node_modules folder for ${packageName}`);
    return null;
  }
  packageName = packageName.replaceAll(path.sep, "/");
  const pnpmNodeModulesPath = path.resolve(nodeModulesPath, ".pnpm");
  const isPnpmModules = await fs.access(pnpmNodeModulesPath).then(
    () => true,
    () => false
  );
  let packagePath = null;
  if (isPnpmModules && !IS_BUN_PM) {
    logger.debug(`Found pnpm node_modules folder for ${packageName}`);
    try {
      const { stdout } = await execAsync(`pnpm list ${packageName} --json`, { cwd: currentPath });
      const packages = JSON.parse(stdout);
      const packageInfo = Array.isArray(packages) ? packages[0] : packages;
      packagePath = packageInfo.dependencies[packageName].path;
    } catch (error) {
      logger.error("", error);
    }
  } else {
    const candidatePath = path.join(nodeModulesPath, packageName);
    logger.debug(`Checking for ${packageName} in ${candidatePath}`);
    try {
      await fs.access(candidatePath);
      packagePath = candidatePath;
    } catch (error) {
    }
  }
  if (packagePath) {
    return path.relative(process.cwd(), packagePath);
  }
  const parentPath = path.resolve(nodeModulesPath, "..");
  return parentPath !== currentPath ? findPackagePath(packageName, parentPath) : null;
}
function getSage(commandConfig, config) {
  if (!config) {
    config = getConfig();
  }
  if (commandConfig?.sage === false || commandConfig?.sage === void 0 && config?.sage === false) {
    return {
      defer: false,
      deferBuffer: 0,
      ephemeral: false,
      errorReplies: false
    };
  }
  return {
    ...DEFAULT_CONFIG.sage,
    ...config?.sage === false ? {} : commandConfig?.sage ?? config?.sage ?? {}
  };
}
async function getWatchedPlugins(config) {
  const pluginNames = config.plugins?.map((plugin) => typeof plugin === "string" ? plugin : plugin[0]) ?? [];
  const watchedPlugins = {};
  for (const name of pluginNames) {
    try {
      const packagePath = await findPackagePath(name, process.cwd());
      const watchFilePath = path.join(packagePath, ".robo", "watch.mjs");
      const importPath = pathToFileURL(path.join(process.cwd(), watchFilePath)).toString();
      await import(importPath);
      watchedPlugins[watchFilePath] = { importPath, name };
    } catch (error) {
    }
  }
  return watchedPlugins;
}
function hasProperties(obj, props) {
  return typeof obj === "object" && obj !== null && props.every((prop) => prop in obj);
}
async function locateInHierarchy(targetPath, currentDir = process.cwd()) {
  const currentPath = path.join(currentDir, targetPath);
  try {
    await fs.access(currentPath);
    return currentPath;
  } catch {
    const upperDir = path.dirname(currentDir);
    if (upperDir === currentDir) {
      return void 0;
    }
    return locateInHierarchy(targetPath, upperDir);
  }
}
function replaceSrcWithBuildInRecord(record, basePath = process.cwd()) {
  const result = {};
  for (const [key, values] of Object.entries(record)) {
    result[key] = values.map((value) => {
      const relativePath = path.relative(basePath, value);
      if (relativePath.startsWith("src/")) {
        return relativePath.replace("src/", "../.robo/build/");
      }
      return "../" + relativePath;
    });
  }
  return result;
}
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
const IS_WINDOWS = /^win/.test(process.platform);
function timeout(callback, ms) {
  return new Promise(
    (resolve) => setTimeout(() => {
      resolve(callback());
    }, ms)
  );
}
function waitForExit(child) {
  return new Promise((resolve) => {
    if (!child) {
      resolve();
    } else if (child.exitCode !== null) {
      resolve();
    } else {
      child.on("exit", resolve);
    }
  });
}
function pLimit(concurrency) {
  if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
    throw new TypeError("Expected `concurrency` to be a number from 1 and up");
  }
  const queue = [];
  let activeCount = 0;
  const next = () => {
    activeCount--;
    if (queue.length > 0) {
      const { task, args, resolve } = queue.shift();
      run(task, resolve, args);
    }
  };
  const run = async (fn, resolve, args) => {
    activeCount++;
    const result = fn(...args);
    resolve(result);
    try {
      await result;
    } catch {
    }
    next();
  };
  const enqueue = (fn, resolve, args) => {
    queue.push({ task: fn, args, resolve });
    (async () => {
      await Promise.resolve();
      if (activeCount < concurrency && queue.length > 0) {
        const { task, args: args2, resolve: resolve2 } = queue.shift();
        run(task, resolve2, args2);
      }
    })();
  };
  const generator = (fn, ...args) => new Promise((resolve) => {
    enqueue(fn, resolve, args);
  });
  Object.defineProperties(generator, {
    activeCount: {
      get: () => activeCount
    },
    pendingCount: {
      get: () => queue.length
    },
    clearQueue: {
      value: () => {
        queue.length = 0;
      }
    }
  });
  return generator;
}

export { IS_WINDOWS, PackageDir, __DIRNAME, cleanTempDir, copyDir, copyToClipboard, pLimit as default, exec, filterExistingPaths, findNodeModules, findPackagePath, getPodStatusColor, getRoboPackageJson, getSage, getTempDir, getWatchedPlugins, hasProperties, locateInHierarchy, openBrowser, packageJson, replaceSrcWithBuildInRecord, sleep, timeout, waitForExit };
